﻿#include <QDebug>
#include <QSettings>
#include <QStringList>
#include <QQmlEngine>
#include <QStorageInfo>
#include <QRegularExpression>

#include "MapEngineManager.h"
#include "../MapEngine.h"
#include "../MapEngineData.h"
#include "../CachedTileSet.h"
#include "../UrlFactory.h"
// #include "CustomLocationPlugin/MapEngine.h"
// #include "CustomLocationPlugin/MapEngineData.h"
// #include "CustomLocationPlugin/CachedTileSet.h"
// #include "CustomLocationPlugin/UrlFactory.h"

static const char* kQmlOfflineMapKeyName = "MapDownload";  // "QGCOfflineMap";

static const auto kElevationMapType = UrlFactory::kCopernicusElevationProviderKey;

MapEngineManager::MapEngineManager(QObject *parent)
    : QObject{parent}
{
    QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
    connect(getMapEngine(), &MapEngine::updateTotals, this, &MapEngineManager::_updateTotals);
    _updateDiskFreeSpace();
}

MapEngineManager::~MapEngineManager()
{
    _tileSets.clear();
}

/**
 * @brief 用于删除、导入、导出
 */
void MapEngineManager::loadTileSets()
{
    if(_tileSets.count()) {
        _tileSets.clear();
        emit tileSetsChanged();
    }

    FetchTileSetTask* task = new FetchTileSetTask();
    connect(task, &FetchTileSetTask::tileSetFetched, this, &MapEngineManager::_tileSetFetched);
    connect(task, &MapTask::error, this, &MapEngineManager::taskError);
    getMapEngine()->addTask(task);
}

void MapEngineManager::updateForCurrentView(double lon0, double lat0, double lon1, double lat1, int minZoom, int maxZoom, const QString &mapName)
{
    _topleftLat     = lat0;
    _topleftLon     = lon0;
    _bottomRightLat = lat1;
    _bottomRightLon = lon1;
    _minZoom        = minZoom;
    _maxZoom        = maxZoom;

    _imageSet.clear();
    _elevationSet.clear();

    for(int z = minZoom; z <= maxZoom; z++) {
        TileSet set = MapEngine::getTileCount(z, lon0, lat0, lon1, lat1, mapName);
        _imageSet += set;
    }
    if (_fetchElevation) {
        TileSet set = MapEngine::getTileCount(1, lon0, lat0, lon1, lat1, kElevationMapType);
        _elevationSet += set;
    }

    emit tileCountChanged();
    emit tileSizeChanged();

    qDebug() << "updateForCurrentView" << lat0 << lon0 << lat1 << lon1 << minZoom << maxZoom;
}

void MapEngineManager::startDownload(const QString &name, const QString &mapType)
{
    if(_imageSet.tileSize) {
        CachedTileSet* set = new CachedTileSet(name);
        set->setMapTypeStr(mapType);
        set->setTopleftLat(_topleftLat);
        set->setTopleftLon(_topleftLon);
        set->setBottomRightLat(_bottomRightLat);
        set->setBottomRightLon(_bottomRightLon);
        set->setMinZoom(_minZoom);
        set->setMaxZoom(_maxZoom);
        set->setTotalTileSize(_imageSet.tileSize);
        set->setTotalTileCount(static_cast<quint32>(_imageSet.tileCount));
        set->setType(mapType);
        CreateTileSetTask* task = new CreateTileSetTask(set);
        //-- Create Tile Set (it will also create a list of tiles to download)
        connect(task, &CreateTileSetTask::tileSetSaved, this, &MapEngineManager::_tileSetSaved);
        connect(task, &MapTask::error, this, &MapEngineManager::taskError);
        getMapEngine()->addTask(task);
    } else {
        qWarning() <<  "MapEngineManager::startDownload() No Tiles to save";
    }
    if (mapType != kElevationMapType && _fetchElevation) {
        CachedTileSet* set = new CachedTileSet(name + " Elevation");
        set->setMapTypeStr(kElevationMapType);
        set->setTopleftLat(_topleftLat);
        set->setTopleftLon(_topleftLon);
        set->setBottomRightLat(_bottomRightLat);
        set->setBottomRightLon(_bottomRightLon);
        set->setMinZoom(1);
        set->setMaxZoom(1);
        set->setTotalTileSize(_elevationSet.tileSize);
        set->setTotalTileCount(static_cast<quint32>(_elevationSet.tileCount));
        set->setType(kElevationMapType);
        CreateTileSetTask* task = new CreateTileSetTask(set);
        //-- Create Tile Set (it will also create a list of tiles to download)
        connect(task, &CreateTileSetTask::tileSetSaved, this, &MapEngineManager::_tileSetSaved);
        connect(task, &MapTask::error, this, &MapEngineManager::taskError);
        getMapEngine()->addTask(task);
    } else {
        qWarning() <<  "MapEngineManager::startDownload() No Tiles to save";
    }
}

void MapEngineManager::saveSetting(const QString &key, const QString &value)
{
    QSettings settings;
    settings.beginGroup(kQmlOfflineMapKeyName);
    settings.setValue(key, value);
}

QString MapEngineManager::loadSetting(const QString &key, const QString &defaultValue)
{
    QSettings settings;
    settings.beginGroup(kQmlOfflineMapKeyName);
    return settings.value(key, defaultValue).toString();
}

void MapEngineManager::deleteTileSet(CachedTileSet *tileSet)
{
    qDebug() << "Deleting tile set " << tileSet->name();
    //-- If deleting default set, delete it all
    if(tileSet->defaultSet()) {
        for(int i = 0; i < _tileSets.count(); i++ ) {
            CachedTileSet* set = qobject_cast<CachedTileSet*>(_tileSets.get(i));
            if(set) {
                set->setDeleting(true);
            }
        }
        ResetTask* task = new ResetTask();
        connect(task, &ResetTask::resetCompleted, this, &MapEngineManager::_resetCompleted);
        connect(task, &MapTask::error, this, &MapEngineManager::taskError);
        getMapEngine()->addTask(task);
    } else {
        tileSet->setDeleting(true);
        DeleteTileSetTask* task = new DeleteTileSetTask(tileSet->setID());
        connect(task, &DeleteTileSetTask::tileSetDeleted, this, &MapEngineManager::_tileSetDeleted);
        connect(task, &MapTask::error, this, &MapEngineManager::taskError);
        getMapEngine()->addTask(task);
    }
}

void MapEngineManager::renameTileSet(CachedTileSet *tileSet, QString newName)
{
    //-- Name must be unique
    int idx = 1;
    QString name = newName;
    while(findName(name)) {
        name = QString("%1 (%2)").arg(newName).arg(idx++);
    }
    qDebug() << "Renaming tile set " << tileSet->name() << "to" << name;
    tileSet->setName(name);
    RenameTileSetTask* task = new RenameTileSetTask(tileSet->setID(), name);
    connect(task, &MapTask::error, this, &MapEngineManager::taskError);
    getMapEngine()->addTask(task);
    emit tileSet->nameChanged();
}

QString MapEngineManager::getUniqueName()
{
    QString test = "Tile Set ";
    QString name;
    int count = 1;
    while (true) {
        name = test;
        name += QString::asprintf("%03d", count++);
        if(!findName(name))
            return name;
    }
}

bool MapEngineManager::findName(const QString &name)
{
    for(int i = 0; i < _tileSets.count(); i++ ) {
        CachedTileSet* set = qobject_cast<CachedTileSet*>(_tileSets.get(i));
        if (set && set->name() == name) {
            return true;
        }
    }
    return false;
}

void MapEngineManager::selectAll()
{
    for(int i = 0; i < _tileSets.count(); i++ ) {
        CachedTileSet* set = qobject_cast<CachedTileSet*>(_tileSets.get(i));
        if(set) {
            set->setSelected(true);
        }
    }
}

void MapEngineManager::selectNone()
{
    for(int i = 0; i < _tileSets.count(); i++ ) {
        CachedTileSet* set = qobject_cast<CachedTileSet*>(_tileSets.get(i));
        if(set) {
            set->setSelected(false);
        }
    }
}

bool MapEngineManager::exportSets(QString path)
{
    _importAction = ActionNone;
    emit importActionChanged();
    QString dir = path;
    if(dir.isEmpty()) {

        dir = QString(); //-- TODO: QGCQFileDialog::getSaveFileName(
        //    MainWindow::instance(),
        //    "Export Tile Set",
        //    QDir::homePath(),
        //    "Tile Sets (*.qgctiledb)",
        //    "qgctiledb",
        //    true);
    }
    if(!dir.isEmpty()) {
        QVector<CachedTileSet*> sets;
        for(int i = 0; i < _tileSets.count(); i++ ) {
            CachedTileSet* set = qobject_cast<CachedTileSet*>(_tileSets.get(i));
            if(set->selected()) {
                sets.append(set);
            }
        }
        if(sets.count()) {
            _importAction = ActionExporting;
            emit importActionChanged();
            ExportTileTask* task = new ExportTileTask(sets, dir);
            connect(task, &ExportTileTask::actionCompleted, this, &MapEngineManager::_actionCompleted);
            connect(task, &ExportTileTask::actionProgress, this, &MapEngineManager::_actionProgressHandler);
            connect(task, &MapTask::error, this, &MapEngineManager::taskError);
            getMapEngine()->addTask(task);
            return true;
        }
    }
    return false;
}

bool MapEngineManager::importSets(QString path)
{
    _importAction = ActionNone;
    emit importActionChanged();
    QString dir = path;
    if(dir.isEmpty()) {
        dir = QString(); //-- TODO: QGCQFileDialog::getOpenFileName(
        //    nullptr,
        //    "Import Tile Set",
        //    QDir::homePath(),
        //    "Tile Sets (*.qgctiledb)");
    }
    if(!dir.isEmpty()) {
        _importAction = ActionImporting;
        emit importActionChanged();
        ImportTileTask* task = new ImportTileTask(dir, _importReplace);
        connect(task, &ImportTileTask::actionCompleted, this, &MapEngineManager::_actionCompleted);
        connect(task, &ImportTileTask::actionProgress, this, &MapEngineManager::_actionProgressHandler);
        connect(task, &MapTask::error, this, &MapEngineManager::taskError);
        getMapEngine()->addTask(task);
        return true;
    }
    return false;
}

void MapEngineManager::resetAction()
{
    _importAction = ActionNone;
    emit importActionChanged();
}

QString MapEngineManager::tileCountStr() const
{
    return MapEngine::numberToString(_imageSet.tileCount + _elevationSet.tileCount);
}

QString MapEngineManager::tileSizeStr() const
{
    return MapEngine::bigSizeToString(_imageSet.tileSize + _elevationSet.tileSize);
}

QStringList MapEngineManager::mapList()
{
    return getMapEngine()->getMapNameList();
}

QStringList MapEngineManager::mapProviderList()
{
    // Extract Provider name from MapName ( format : "Provider Type")
    QStringList mapList = getMapEngine()->getMapNameList();
    mapList.replaceInStrings(QRegExp("^([^\\ ]*) (.*)$"),"\\1");
    mapList.removeDuplicates();
    return mapList;
}

QStringList MapEngineManager::mapTypeList(QString provider)
{
    // Extract type name from MapName ( format : "Provider Type")
    QStringList mapList = getMapEngine()->getMapNameList();
    mapList = mapList.filter(QRegularExpression(provider));
    mapList.replaceInStrings(QRegExp("^([^\\ ]*) (.*)$"),"\\2");
    mapList.removeDuplicates();
    return mapList;
}

quint32 MapEngineManager::maxMemCache()
{
    return getMapEngine()->getMaxMemCache();
}

quint32 MapEngineManager::maxDiskCache()
{
    return getMapEngine()->getMaxDiskCache();
}

int MapEngineManager::selectedCount()
{
    int count = 0;
    for(int i = 0; i < _tileSets.count(); i++ ) {
        CachedTileSet* set = qobject_cast<CachedTileSet*>(_tileSets.get(i));
        if(set && set->selected()) {
            count++;
        }
    }
    return count;
}

void MapEngineManager::setMaxMemCache(quint32 size)
{
    getMapEngine()->setMaxMemCache(size);
}

void MapEngineManager::setMaxDiskCache(quint32 size)
{
    getMapEngine()->setMaxDiskCache(size);
}

void MapEngineManager::taskError(MapTask::TaskType type, QString error)
{
    QString task;
    switch(type) {
    case MapTask::taskFetchTileSets:
        task = "Fetch Tile Set";
        break;
    case MapTask::taskCreateTileSet:
        task = "Create Tile Set";
        break;
    case MapTask::taskGetTileDownloadList:
        task = "Get Tile Download List";
        break;
    case MapTask::taskUpdateTileDownloadState:
        task = "Update Tile Download Status";
        break;
    case MapTask::taskDeleteTileSet:
        task = "Delete Tile Set";
        break;
    case MapTask::taskReset:
        task = "Reset Tile Sets";
        break;
    case MapTask::taskExport:
        task = "Export Tile Sets";
        break;
    default:
        task = "Database Error";
        break;
    }
    QString serror = "Error in task: " + task;
    serror += "\nError description:\n";
    serror += error;
    qWarning() << "MapEngineManager::_taskError()" << error;
    setErrorMessage(serror);
}

/**
 * @brief 普通的拖动地图来触发的下载行为不会调用该方法
 *        该方法应该只用于离线缓存功能
 * @param set
 */
void MapEngineManager::_tileSetSaved(CachedTileSet *set)
{
    qDebug() << "New tile set saved (" << set->name() << "). Starting download...";
    _tileSets.append(set);
    emit tileSetsChanged();
    //-- Start downloading tiles
    set->createDownloadTask();
}

/**
 * @brief 用于删除、导入、导出
 *        在 loadTileSets 中被信号连接
 */
void MapEngineManager::_tileSetFetched(CachedTileSet *tileSet)
{
    //-- A blank (default) type means it uses various types and not just one
    if(tileSet->type() == "Invalid") {
        tileSet->setMapTypeStr("Various");
    }
    _tileSets.append(tileSet);
    tileSet->setManager(this);
    emit tileSetsChanged();
}

void MapEngineManager::_tileSetDeleted(quint64 setID)
{
    //-- Tile Set successfully deleted
    CachedTileSet* setToDelete = nullptr;
    int i = 0;
    for(i = 0; i < _tileSets.count(); i++ ) {
        CachedTileSet* set = qobject_cast<CachedTileSet*>(_tileSets.get(i));
        if (set && set->setID() == setID) {
            setToDelete = set;
            break;
        }
    }
    if(setToDelete) {
        _tileSets.removeAt(i);
        delete setToDelete;
    }
    emit tileSetsChanged();
}

void MapEngineManager::_updateTotals(quint32 totaltiles, quint64 totalsize, quint32 defaulttiles, quint64 defaultsize)
{
    for(int i = 0; i < _tileSets.count(); i++ ) {
        CachedTileSet* set = qobject_cast<CachedTileSet*>(_tileSets.get(i));
        if (set && set->defaultSet()) {
            set->setSavedTileSize(totalsize);
            set->setSavedTileCount(totaltiles);
            set->setTotalTileCount(defaulttiles);
            set->setTotalTileSize(defaultsize);
            return;
        }
    }
    _updateDiskFreeSpace();
}

/**
 * @brief 用于 删除图集
 */
void MapEngineManager::_resetCompleted()
{
    //-- Reload sets
    loadTileSets();
}

/**
 * @brief 用于 导入 & 导出
 */
void MapEngineManager::_actionCompleted()
{
    ImportAction oldState = _importAction;
    _importAction = ActionDone;
    emit importActionChanged();
    //-- If we just imported, reload it all
    if(oldState == ActionImporting) {
        loadTileSets();
    }
}

/**
 * @brief 用于 导入 & 导出
 */
void MapEngineManager::_actionProgressHandler(int percentage)
{
    _actionProgress = percentage;
    emit actionProgressChanged();
}

void MapEngineManager::_updateDiskFreeSpace()
{
    QString path = getMapEngine()->getCachePath();
    if(!path.isEmpty()) {
        QStorageInfo info(path);
        quint32 total = static_cast<quint32>(info.bytesTotal() / 1024);
        quint32 free  = static_cast<quint32>(info.bytesFree()  / 1024);
        qDebug() << info.rootPath() << "has" << free << "Mbytes available.";
        if(_freeDiskSpace != free) {
            _freeDiskSpace = free;
            _diskSpace = total;
            emit freeDiskSpaceChanged();
        }
    }
}
